//
//  XIParticle.m
//  XIEngine
//
//  Created by Balazs Faludi on 07.01.10.
//  Copyright 2010 Universität Basel. All rights reserved.
//

#import "XIParticle.h"
#import "XIEngine.h"
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

#pragma mark -
#pragma mark Particle

XIParticle * XIParticleCreate() {
	XIParticle *p;
	p = (XIParticle *)malloc(sizeof(XIParticle));
	if(!p) return 0;
	p->pos = XIVectorZero;
	p->vec = xiv(0.0f, 1.0f);
	p->acc = XIVectorZero;
	p->angle = 0.0f;
	p->rot = 0.0f;
	p->rotAcc = 0.0f;
	p->color = xiPaint(0.5f, 0.5f, 0.5f, 1.0f);
	p->life = 2.0f;
	p->age = 0.0f;
	p->size = 1.0f;
	p->growth = 0.0f;
	p->emiter = NULL;
	p->phase = 0;
	p->killed = NO;
	p->child = NULL;
	return p;
}

void XIParticleRelease(XIParticle *p) {
	XIParticleRelease(p->child);
	free(p);
}

void XIParticleKill(XIParticle *p) {
	p->killed = YES;
}

#pragma mark -
#pragma mark Stack

XIParticleStack * XIParticleStackCreate() {
	XIParticleStack *stack;
	stack = (XIParticleStack *)malloc(sizeof(XIParticleStack));
	if(!stack) return 0;
	stack->head = 0;
	stack->tail = 0;
	stack->count = 0;
	stack->maxNodes = 1000;
	return stack;
}

BOOL XIParticlePush(XIParticleStack *stack, XIParticle *particle) {
	if(stack->maxNodes >= 0 && stack->count >= stack->maxNodes) return NO;
	XIParticleNode *node;
	node = (XIParticleNode *)malloc(sizeof(XIParticleNode));
	if(!node) return NO;
	node->value = particle;
	if(stack->head == NULL){
		stack->head = node;
		stack->tail = node;
		node->prev = NULL;
		node->next = NULL;
	}
	else {
		node->prev = NULL;
		node->next = stack->head;
		stack->head->prev = node;
		stack->head = node;
	}
		
	stack->count++;
	return YES;
}

BOOL XIParticlePop(XIParticleStack *stack, XIParticle **particle) {
	XIParticleNode *node;
	node = stack->head;
	if(!node) return NO;
	*particle = node->value;
	stack->head = node->next;
	if(stack->head == NULL) stack->tail = NULL; 
	else stack->head->prev = NULL;
	free(node);
	stack->count--;
	return YES;
}

BOOL XIParticleNodeRemove(XIParticleStack *stack, XIParticleNode *node){
	if(stack->head == node) {
		XIParticle *dummy;
		return XIParticlePop(stack, &dummy);
	}
	else if(stack->tail == node) {
		stack->tail = stack->tail->prev;
		stack->tail->next = 0;
		node->prev = 0;
		free(node);
		stack->count--;
	}
	else {
		node->prev->next = node->next;
		node->next->prev = node->prev;
		free(node);
		stack->count--;
	}
	return YES;
}

void XIParticleStackRelease(XIParticleStack *stack) {
	XIParticle *dummy;
	while(XIParticlePop(stack, &dummy));
	free(stack);
}

#pragma mark -
#pragma mark Emiter

XIParticleEmiter * XIParticleEmiterCreate(int capacity) {
	XIParticleEmiter *emiter;
	emiter = (XIParticleEmiter *)malloc(sizeof(XIParticleEmiter));
	if(!emiter) return 0;
	emiter->particles = XIParticleStackCreate();
	emiter->particles->maxNodes = -1;
	emiter->pos = xiv(0.0f, 0.0f);
	emiter->active = NO;
	emiter->texture = 0;
	emiter->emitInterval = 0.016666f;
	emiter->lastEmit = 0;//[[NSDate date] retain];
	emiter->emitAmount = 5;
	emiter->initFunction = NULL;
	emiter->stepFunction = NULL;
	emiter->age = 0;
	emiter->capacity = capacity;
	GLfloat *myVerts = (GLfloat *)malloc(sizeof(GLfloat) * capacity * 12);
	GLfloat *myTexCoords = (GLfloat *)malloc(sizeof(GLfloat) * capacity * 12);
	GLfloat *myColors = (GLfloat *)malloc(sizeof(GLfloat) * capacity * 4);
	emiter->vertices = myVerts;
	emiter->texCoords = myTexCoords;
	emiter->colors = myColors;
	emiter->blendMode = XIParticleEmiterBlendAdd;
	emiter->renderMode = XIParticleEmiterRenderAllAtOnce;
	emiter->type = XIParticleEmiterTypeSprite;
	return emiter;
}

void XIParticleEmiterRelease(XIParticleEmiter *emiter) {
	XIParticleStackRelease(emiter->particles);
	free(emiter->vertices);
	free(emiter->texCoords);
	free(emiter->colors);
	free(emiter);
}

void XIParticleEmiterStart(XIParticleEmiter *emiter) {
	emiter->active = YES;
}

void XIParticleEmiterStop(XIParticleEmiter *emiter) {
	emiter->active = NO;
}

#pragma mark -
#pragma mark Loop

void XIParticleEmiterCheckCapacity(XIParticleEmiter *emiter) {
	if(emiter->particles->count > emiter->capacity) {
		emiter->capacity = emiter->particles->count;
		GLfloat *myVerts = (GLfloat *)realloc(emiter->vertices, sizeof(GLfloat) * emiter->capacity * 12);
		GLfloat *myTexCoords = (GLfloat *)realloc(emiter->texCoords, sizeof(GLfloat) * emiter->capacity * 12);
		GLfloat *myColors = (GLfloat *)realloc(emiter->colors, sizeof(GLfloat) * emiter->capacity * 4);
		emiter->vertices = myVerts;
		emiter->texCoords = myTexCoords;
		emiter->colors = myColors;
	}
}

void XIParticleEmiterAddParticle(XIParticleEmiter *emiter, XIParticle *newParticle) {
	newParticle->emiter = emiter;
	XIParticlePush(emiter->particles, newParticle);
	XIParticleEmiterCheckCapacity(emiter);
}

void XIParticleEmiterEmit(XIParticleEmiter *emiter) {
	for(int i = 0; i < emiter->emitAmount; i++) {
		XIParticle *p = XIParticleCreate();
		XIParticleEmiterAddParticle(emiter, p);
		//p->emiter = emiter;
		p->pos = XIVectorZero;
		//XIParticlePush(emiter->particles, p);
		if(emiter->initFunction != NULL) {
			emiter->initFunction(p);
		} else {
			XIParticleEmiterDefaultInitFunction(p);
		}
	}
	emiter->lastEmit = 0;
}

void XIParticleEmiterStep(XIParticleEmiter *emiter) {
	if(emiter->active == NO) return;
	NSDate *lastUpdate = [[XIGame sharedGame] lastUpdate];
	NSTimeInterval interval = -[lastUpdate timeIntervalSinceNow];
	emiter->age += interval;
	emiter->lastEmit += interval;
	if(emiter->lastEmit > emiter->emitInterval) {
		XIParticleEmiterEmit(emiter);
	}
	XIParticleNode *n = emiter->particles->head;
	while (n != NULL && n->value != NULL) {
		XIParticleNode *next = n->next;
		n->value->age += interval;
		if(emiter->stepFunction != NULL) {
			emiter->stepFunction(n->value);
		} else {
			XIParticleEmiterDefaultStepFunction(n->value);
		}
		if(n->value->killed) {
			XIParticleNodeRemove(emiter->particles, n);
		}
		n = next;
	}
}

BOOL XIParticleEmiterRender(XIParticleEmiter *emiter) {
	
	BOOL rendered = NO;
	
	// Select blend mode.
	if(emiter->blendMode == XIParticleEmiterBlendNone || emiter->type != XIParticleEmiterTypeSprite) {
		//glDisable(GL_BLEND);
		//glEnable(GL_BLEND);
		//glDisable(GL_TEXTURE_2D);
		//glEnableClientState(GL_COLOR_ARRAY);
		//glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		glDisable(GL_TEXTURE_2D);
	} else {
		
		
		switch (emiter->blendMode) {
			case XIParticleEmiterBlendAdd:
				glBlendFunc(GL_SRC_ALPHA,GL_ONE);
				break;
			case XIParticleEmiterBlendMult:
				glBlendFunc(GL_DST_COLOR,GL_ZERO);
				break;
			case XIParticleEmiterBlendInterpolate:
				glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
				break;
			default:
				break;
		}
	}
	glEnable(GL_BLEND);
	if(emiter->type == XIParticleEmiterTypeSprite) {
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, emiter->texture);          // choose particle texture	
	}
		
	
	glPushMatrix();
	glTranslatef(emiter->pos.x, emiter->pos.y, 0.0);
	
	int counter = 0;
	XIParticleNode *n = emiter->particles->head;
	while (n != NULL && n->value != NULL) {
		XIParticle *p = n->value;
		if(p->killed == NO){
			BOOL rendered = YES;
			XIVector pos = p->pos;
			XIFloat off = p->size * 0.5;
			switch (emiter->type) {
				case XIParticleEmiterTypeSprite:
					switch (emiter->renderMode) {
						case XIParticleEmiterRenderSeparate:;
							glPushMatrix();
							glTranslatef(pos.x, pos.y, 0.0f);
							glScalef(off, off, off);
							glRotatef(p->angle, 0, 0, 1);
							glColor4f(p->color.r, p->color.g, p->color.b, p->color.a);
							GLfloat	 coordinates[] = {0, 1, 1, 1, 0, 0, 1, 0};
							GLfloat	vertices[] = {-1, -1, 
								1, -1, 
								-1, 1, 
								1,  1 };
							glVertexPointer(2, GL_FLOAT, 0, vertices);
							glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
							glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
							glPopMatrix();
							break;
						case XIParticleEmiterRenderAllAtOnce:;
							GLfloat verts[] = {
								pos.x - off, pos.y - off,
								pos.x - off, pos.y + off,
								pos.x + off, pos.y - off,
								pos.x + off, pos.y - off,
								pos.x - off, pos.y + off,
								pos.x + off, pos.y + off,
							};
							GLfloat colors[] = {p->color.r, p->color.g, p->color.b, p->color.a};
							GLfloat texCoords[] = {0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1};
							memcpy(emiter->vertices + counter * 12, verts, sizeof(GLfloat) * 12);
							memcpy(emiter->texCoords + counter * 12, texCoords, sizeof(GLfloat) * 12);
							memcpy(emiter->colors + counter * 4, colors, sizeof(GLfloat) * 4);
							break;
						default:
							break;
					}			
					break;
				case XIParticleEmiterTypeLine:;
					XIVector cPos = p->child->pos;
					glPushMatrix();
					glTranslatef(pos.x, pos.y, 0.0f);
					glRotatef(p->angle, 0, 0, 1);
					//glColor4f(p->color.r, p->color.g, p->color.b, p->color.a);
					GLfloat	 colors[] = {p->color.r, p->color.g, p->color.b, p->color.a, 
						p->child->color.r, p->child->color.g, p->child->color.b, p->child->color.a};
					//GLfloat	 coordinates[] = {0, 1, 1, 1, 0, 0, 1, 0};
					GLfloat	vertices[] = {0.0f, 0.0f, cPos.x, cPos.y};
					glColorPointer(4, GL_FLOAT, 0, colors);
					glEnableClientState(GL_COLOR_ARRAY);
					glVertexPointer(2, GL_FLOAT, 0, vertices);
					//glEnableClientState(GL_VERTEX_ARRAY);
					//glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
					glDrawArrays(GL_LINES, 0, 2);
					//glDisableClientState(GL_COLOR_ARRAY);
					//glEnable(GL_TEXTURE_2D);
					glPopMatrix();
					
					break;
				default:
					break;
			}
		}
		counter++;
		n = n->next;
	}
	switch (emiter->type) {
		case XIParticleEmiterTypeSprite:
			switch (emiter->renderMode) {
				case XIParticleEmiterRenderSeparate:
					break;
				case XIParticleEmiterRenderAllAtOnce:
					glVertexPointer(2, GL_FLOAT, 0, emiter->vertices);
					glTexCoordPointer(2, GL_FLOAT, 0, emiter->texCoords);
					glColorPointer(4, GL_FLOAT, 0, emiter->colors);
					glDrawArrays(GL_TRIANGLES, 0, counter * 6);
				default:
					break;
			}
			break;
		case XIParticleEmiterTypeLine:
			break;
			glEnable(GL_TEXTURE_2D);
			glDisableClientState(GL_COLOR_ARRAY);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		default:
			break;
	}
	
	glPopMatrix();
	glDepthMask(GL_TRUE);
	glDisable(GL_BLEND);
	
	return rendered;
}

#pragma mark -
#pragma mark Default Functions

void XIParticleEmiterDefaultInitFunction(XIParticle *p) {
	float spread = XIGetRandomFloat(0.0f, 1.0f);
	float angle  = XIGetRandomAngle(); 
	float x = cosf(angle) * spread;
	float y = sinf(angle) * spread;
	p->vec = xiv(x, y);
	if(p->emiter->type == XIParticleEmiterTypeLine) {
		p->child = XIParticleCreate();
		
	}
}

void XIParticleEmiterDefaultStepFunction(XIParticle *p) {
	XIFloat multiplier = [[XIGame sharedGame] multiplier];
	p->vec = xivadd(p->vec, p->acc);
	p->pos = xivadd(p->pos, xivmult(p->vec, multiplier));
	p->rot += p->rotAcc;
	p->angle += p->rot;
	p->size = max(0.0f, p->size + p->growth);
	if(p->age > p->life) {
		XIParticleKill(p);
	}
}



